﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Text;
using System.Collections.Generic;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

using Shapes.Geometry;
using Shapes.Misc.Appearance;

namespace Shapes.Misc
{
    /// <summary>
    /// A class to create textures (the class is partial so you may extend it)
    /// </summary>
    public partial class TextureGenerator
    {
        GraphicsDevice _Device;
        /// <summary>
        /// The graphics device which is used for the textures
        /// </summary>
        public GraphicsDevice GraphicsDevice { get { return _Device; } set { _Device = value; } }

        PrimitiveDrawer _PrimitiveDrawer;
        public PrimitiveDrawer PrimitiveDrawer { get { return _PrimitiveDrawer; } }

        /// <summary>
        /// creates a new TextureGenerator object.
        /// </summary>
        /// <param name="device">the GraphicsDevice of the game</param>
        public TextureGenerator(GraphicsDevice device)
        {
            _Device = device;
            _PrimitiveDrawer = new PrimitiveDrawer(device);
        }

        #region Shapes
        /// <summary>
        /// Fills the Shape with a Color and returns it as a Texture2D
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilled(Shape geometry)
        {
            return GeometryFilled(geometry, new FillColor(Color.White));
        }        
        /// <summary>
        /// Fills the Shape with a Color and returns it as a Texture2D
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="color">the fill color</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilled(Shape geometry, Color color)
        {
            return GeometryFilled(geometry, new FillColor(color));
        }
        /// <summary>
        /// Fills the Shape with a Color and returns it as a Texture2D
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="fill">the fill</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilled(Shape geometry, Fill fill)
        {
            int width = (int)Math.Ceiling(geometry.Width);
            int height = (int)Math.Ceiling(geometry.Height);

            Texture2D texture = new Texture2D(_Device, width, height);
            Color[] pixels = new Color[width * height];
            texture.GetData<Color>(pixels);

            Vector2 v = new Vector2();
            for (int i = 0; i < pixels.Length; i++)
            {
                v.X = (i % width);              // + geometry._Bounding._Position.X;
                v.Y = (float)(i - i % width) / width; // + geometry._Bounding._Position.Y;

                //// to show bounding...
                //pixels[i] = new Color(0,0,0, 128);

                if (geometry.IsPointInside(ref v))
                    pixels[i] = fill.GetColorAt(v);
            }
            texture.SetData<Color>(pixels);

            return texture;
        }
        /// <summary>
        /// Creates the border along the edges of the Shape with a Brush and returns it as a Texture2D
        /// </summary>
        /// <param name="geometry">the drawing or shape to draw</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryBorder(Drawing geometry)
        {
            return GeometryBorder(geometry, Brush.StandardBrush);
        }
        /// <summary>
        /// Creates the border along the edges of the Shape with a Brush and returns it as a Texture2D
        /// </summary>
        /// <param name="geometry">the drawing or shape to draw</param>
        /// <param name="brush">the brush for the border</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryBorder(Drawing geometry, Brush brush)
        {
            Vector2 offset = brush.GetTextureOffset();
            Texture2D texture = new Texture2D(_Device,
                (int)(geometry.Width + 2 * Math.Ceiling(offset.X)),
                (int)(geometry.Height + 2 * Math.Ceiling(offset.Y)));

            Color[] pixels = new Color[texture.Width * texture.Height];
            texture.GetData<Color>(pixels);

            Vector2 v = new Vector2();

            for (int i = 0; i < pixels.Length; i++)
            {
                v.X = (i % texture.Width) - offset.X;
                v.Y = (float)(i - i % texture.Width) / texture.Width - offset.Y;

                pixels[i] = brush.GetColorAt(geometry, ref v);
            }

            texture.SetData<Color>(pixels);

            return texture;
        }
        
        /// <summary>
        /// Creates a filled Texture and a Border of the Shape and combinds both to a single Texture
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="fillColor">the fill color</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilledWithBorder(Shape geometry, Color fillColor)
        {
            return GeometryFilledWithBorder(geometry, new FillColor(fillColor), Brush.StandardBrush);
        }
        /// <summary>
        /// Creates a filled Texture and a Border of the Shape and combinds both to a single Texture
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="fill">the fill</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilledWithBorder(Shape geometry, Fill fill)
        {
            return GeometryFilledWithBorder(geometry, fill, Brush.StandardBrush);
        }
        /// <summary>
        /// Creates a filled Texture and a Border of the Shape and combinds both to a single Texture
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="fillColor">the fill color</param>
        /// <param name="brush">the brush for the border</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilledWithBorder(Shape geometry, Color fillColor, Brush brush)
        {
            return GeometryFilledWithBorder(geometry, new FillColor(fillColor), brush);
        }
        /// <summary>
        /// Creates a filled Texture and a Border of the Shape and combinds both to a single Texture
        /// </summary>
        /// <param name="geometry">the shape to draw</param>
        /// <param name="fill">the fill</param>
        /// <param name="brush">the brush for the border</param>
        /// <returns>the generated texture</returns>
        public Texture2D GeometryFilledWithBorder(Shape geometry, Fill fill, Brush brush)
        {
            Texture2D spriteA = GeometryFilled(geometry, fill);
            Vector2 posA = brush.GetTextureOffset();

            Texture2D spriteB = GeometryBorder(geometry, brush);
            Point posB = Point.Zero;

            return CombineTwoTexturesByPixel(spriteA, new Point((int)posA.X, (int)posA.Y), spriteB, posB);
        }
        #endregion

        /// <summary>
        /// Combines two Textures in a slow CPU algorithm. It is better than a drawtarget because there are no alpha channel bugs.
        /// </summary>
        /// <param name="below">the first below</param>
        /// <param name="belowPosition">the position of the first texture</param>
        /// <param name="above">the second texture</param>
        /// <param name="abovePosition">the position of the second texture</param>
        /// <returns>the generated texture</returns>
        public Texture2D CombineTwoTexturesByPixel(Texture2D below, Point belowPosition, Texture2D above, Point abovePosition)
        {
            Point dim = new Point(
                Math.Max(below.Width + belowPosition.X, above.Width + abovePosition.X),
                Math.Max(below.Height + belowPosition.Y, above.Height + abovePosition.Y));

            Texture2D result = new Texture2D(_Device, dim.X, dim.Y);
            
            Color[] pixelsBelow = new Color[below.Width * below.Height];
            Color[] pixelsAbove = new Color[above.Width * above.Height];
            Color[] pixelsResult = new Color[dim.X * dim.Y];

            below.GetData<Color>(pixelsBelow);
            above.GetData<Color>(pixelsAbove);


            Point v, vBelow, vAbove;
            int idxBelow, idxAbove;

            for (int i = 0; i < pixelsResult.Length; i++)
            {
                v.X = (i + 1) % result.Width;
                v.Y = ((i - v.X + 1) / result.Width) + 1;

                vBelow = new Point(v.X - belowPosition.X, v.Y - belowPosition.Y);
                vAbove = new Point(v.X - abovePosition.X, v.Y - abovePosition.Y);

                idxBelow = GetPixelIndex(vBelow.X, vBelow.Y, below.Width, below.Height); 
                idxAbove = GetPixelIndex(vAbove.X, vAbove.Y, above.Width, above.Height);

#if XNA4
                Color color = Color.Transparent;
#else
                Color color = Color.TransparentBlack;
#endif

                if ((idxAbove >= 0) && (idxAbove < pixelsAbove.Length))
                {
                    if ((idxBelow >= 0) && (idxBelow < pixelsBelow.Length) 
                        && (pixelsBelow[idxBelow].A != 0))
                    {
                        float a = pixelsAbove[idxAbove].A / 255f;
                        Vector4 col = new Vector4();

                        col.X = pixelsBelow[idxBelow].R * (1 - a) + pixelsAbove[idxAbove].R * a;
                        col.Y = pixelsBelow[idxBelow].G * (1 - a) + pixelsAbove[idxAbove].G * a;
                        col.Z = pixelsBelow[idxBelow].B * (1 - a) + pixelsAbove[idxAbove].B * a;
                        col.W = pixelsBelow[idxBelow].A + pixelsAbove[idxAbove].A;

                        color = new Color(col / 255);
                    }
                    else
                    {
                        color = pixelsAbove[idxAbove];
                        color = new Color(color.R, color.G, color.B, (byte)(color.A));
                    }
                }
                else
                {
                    if ((idxBelow >= 0) && (idxBelow < pixelsBelow.Length))
                    {
                        color = pixelsBelow[idxBelow];
                        color = new Color(color.R, color.G, color.B, (byte)(color.A));
                    }
                }
                
                pixelsResult[i] = color;
            }
            
            result.SetData<Color>(pixelsResult);
            return result;
        }

        internal static int GetPixelIndex(int positionX, int positionY, int textureWidth, int textureHeight)
        {
            if((positionX < 0 || positionY < 0) || (positionX >= textureWidth || positionY >= textureHeight))
                return -1;

            return (int)((positionY ) * textureWidth + positionX) ;
        }

    
    }
}
